package Logging;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;

/**
 * Monitor class which allows logging server information into
 * a file. This class is a singleton, the instance can be received by
 * calling the static get method.
 */
public class Monitor {
	
  private FileWriter fw;
  private PrintWriter pw;
  private int ln = 0;
  private final String LogFilePath = "c://temp/log.txt";
  private final boolean CAN_WRITE = true; 
  
  public Monitor() {
    try {
      fw = new FileWriter(LogFilePath, true);
      pw = new PrintWriter(fw, true);
      if(CAN_WRITE)
    	  pw.println("====== Started at " + new Date() + " ======");
    } catch (IOException e) {
    }
  }
  
  private static Monitor inst;
  
  public synchronized static Monitor get() {
    if (inst == null) {
      inst = new Monitor();
    }
    return inst;
  }
  
  /**
   * Writes simple data to log.
   * 
   * @param o
   *          the object which initiated the logging
   * @param arr
   *          the list of data to log
   */
  public static void write(Object o, Object... arr) {
    get().writeImpl(o, arr);
  }
  
  /**
   * Writes exception data to log
   * 
   * @param e
   *          the exception to be logged
   */
  public static void writeException(Exception e) {
    get().writeExp(e);
  }
  
  /**
   * Writes the exception stack trace to log
   * 
   * @param e
   *          the exception to be logged
   */
  private void writeExp(Exception e) {
    if (pw == null || !CAN_WRITE) {
      return;
    }
    e.printStackTrace(pw);
    pw.flush();
  }
  
  /**
   * Writes the data to the log
   * 
   * @param o
   *          the object which initialized the logging
   * @param arr
   *          the data to log
   */
  private void writeImpl(Object o, Object... arr) {
    if (pw == null || !CAN_WRITE) {
      return;
    }
    StringBuilder sb = new StringBuilder(100);
    ++ln;
    sb.append("(" + ln + ") ");
    if(o != null) {
    	sb.append(o.getClass().getSimpleName());
    }
    for (Object x : arr) {
      sb.append(" ").append(str(x));
    }
    pw.println(sb.toString());
    pw.flush();
  }
  
  /**
   * Converts the received object to string depending on its type
   * 
   * @param x
   *          the object to convert
   * @return the string representation of that object
   */
  private String str(Object x) {
    if (x instanceof Iterable<?>) {
      return strList((Iterable<?>) x);
    }
    if (x instanceof Object[]) {
      return Arrays.toString((Object[]) x);
    }
    if (x instanceof Map<?, ?>) {
      return strMap((Map<?, ?>) x);
    }
    return String.valueOf(x);
  }
  
  /**
   * Converts a map object to a printable string
   * 
   * @param x
   *          the map object
   * @return the string representation of the received object
   */
  private String strMap(Map<?, ?> x) {
    StringBuilder sb = new StringBuilder("{");
    int n = 0;
    for (Map.Entry<?, ?> e : x.entrySet()) {
      if (++n > 1) {
        sb.append(", ");
      }
      sb.append(str(e.getKey() + "=" + str(e.getValue())));
    }
    sb.append("}");
    return sb.toString();
  }
  
  /**
   * Converts a iterable object to a printable string
   * 
   * @param x
   *          the iterable object
   * @return the string representation of the received object
   */
  private String strList(Iterable<?> x) {
    StringBuilder sb = new StringBuilder("[");
    int n = 0;
    for (Object o : x) {
      if (++n > 1) {
        sb.append(", ");
      }
      sb.append(str(o));
    }
    sb.append("]");
    return sb.toString();
  }
}
